---
sidebar_position: 2.4
description: SpringCloudGateway
---

网关能实现功能微服务的路由转发，将请求通过规则引擎正确的转发到对应的微服务完成响应，并最终返回给客户端。
## 简介
* SpringCloudGateway是Spring官网最新推出的一款基于Spring Framework 5，Project Reactor和SpringBoot2之上开发的网关，取代Zuul。
* 它与第一代网关Zuul不同的是：gateway是异步非阻塞的（netty+webflux实现）；zuul是同步阻塞请求的。
### Gateway 三大组成部分
![](./img/SpringCloudGateway1.png)
#### 路由：它指定了要路由的微服务，
路由由两部分组成，ID和目标URI。<br/>
ID在网关的配置中必须是全局唯一的，类似于数据库中的主键的概念，即数据库中的每一条记录都要有ID去标识它。<br/>
URI就是需要路由到的微服务的地址或者说是服务名称
#### Predicate断言
Java8接口中提供的Predicate接口，Gateway使用Predicate实现了路由的匹配条件。
#### Filter过滤器
与Servlet编程模型中的过滤器概念类似，同时可以用于修改请求以及响应数据。可以利用filter实现鉴权、访问日志记录、接口耗时记录等。

:::tip
Predicate和Filter都是Route的组成部分。也可以说是Route的一些条件，需要共同作用产生效果。
:::
### Gateway的工作模型
* 请求发送到网关（所有向SpringCloud发请求的，都叫做GatewayClient），经由分发器将请求匹配到相应的HandlerMapping（第一个分析匹配的组件）。
* 请求和处理器之间有一个映射，路由到网关处理程序，即Web Handler,最重要的功能是读取路由上配置的所有过滤器去构造过滤器链，将请求往过滤器链上传递。
* 执行特定的请求过滤器链。客户端请求将会按照优先级依次执行各个过滤器。
* 最终到达代理的微服务。（微服务处理完请求后如何返回客户端？）

![](./img/SpringCloudGateway2.png)

## 谓词Predicate的原理和应用
Predicate是Gateway路由的组成部分之一，是对条件匹配的判断。

Predicate由Java8引入，位于java.util.function包中，是一个FunctionalInterface（函数式接口）
```java

/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 *
 * @param <T> the type of the input to the predicate
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
```
 Predicate需要一个输入参数，通常用在stream的filter中，表示是否满足过来条件，返回boolean类型

```java
package com.wkq.commerce.service;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * <h1>Java 8 Predicate 使用方法与思想</h1>
 */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class PredicateTest {
    public static List<String> MICRO_SERVICE = Arrays.asList(
            "nacos", "authority", "gateway", "ribbon", "feign", "hystrix", "commerce"
    );

    /**
     * <h2>test 方法主要用于参数符不符合规则，返回值是 boolean</h2>
     */
    @Test
    public void testPredicateTest() {
        Predicate<String> letterLengthLimit = s -> s.length() > 5;
        //Predicate的使用方法就是填充到 filter 里面
        List<String> list = MICRO_SERVICE.stream().filter(letterLengthLimit).collect(Collectors.toList());
        System.out.println("list = " + list);//list = [authority, gateway, ribbon, hystrix, commerce]
    }

    /**
     * <h2> and 方法等同于逻辑与 &&  ，存在短路特性，需要所有的条件都满足才可以。结合其他的Predicate去使用</h2>
     */
    @Test
    public void testPredicateAnd() {
        Predicate<String> letterLengthLimit = s -> s.length() > 5;
        Predicate<String> letterStartWith = s -> s.startsWith("gate");
        List<String> list = MICRO_SERVICE.stream().filter(letterLengthLimit.and(letterStartWith)).collect(Collectors.toList());
        System.out.println("list = " + list);//list = [gateway]
    }

    /**
     * <h2>or 等同于我们的逻辑或 || ，多个条件只要一个满足即可</h2>
     */
    @Test
    public void testPredicateOr() {
        Predicate<String> letterLengthLimit = s -> s.length() > 5;
        Predicate<String> letterStartWith = s -> s.startsWith("fe");
        List<String> list = MICRO_SERVICE.stream().filter(letterLengthLimit.or(letterStartWith)).collect(Collectors.toList());
        System.out.println("list = " + list);//list = [authority, gateway, ribbon, feign, hystrix, commerce]
    }


    /**
     * <h2>negate 等同于逻辑非 !</h2>
     */
    @Test
    public void testPredicateNegate() {
        Predicate<String> letterLengthLimit = s -> s.length() > 5;
        Predicate<String> letterStartWith = s -> s.startsWith("gate");
        List<String> list = MICRO_SERVICE.stream().filter(letterLengthLimit.and(letterStartWith.negate())).collect(Collectors.toList());
        System.out.println("list = " + list);//list = [authority, gateway, ribbon, feign, hystrix, commerce]
    }


    /**
     * <h2>isEqual 类似于 Object 里面的 equals 方法，区别在于：先判断对象是否为null，如果不为null，再使用</h2>
     */
    @Test
    public void testPredicateIsEqual() {
        Predicate<String> letterEqualWith = s -> Predicate.isEqual("gateway").test(s);//判断是否等于gateway
        List<String> list = MICRO_SERVICE.stream().filter(letterEqualWith).collect(Collectors.toList());
        System.out.println("list = " + list);//list = [authority, gateway, ribbon, feign, hystrix, commerce]
    }
}
```
## 集成Nacos实现动态路由配置
将 Gateway 的路由配置信息存储在 Alibaba Nacos，使用的是Nacos 的高内聚和低耦合的特性。<br/>
### 静态路由配置
* 静态路由配置写在配置文件中（yml或者properties文件中），端点是：spring.cloud.gateway
```yaml
# 静态路由
   gateway:
     routes:
       - id: path_route # 路由的ID
         uri: 127.0.0.1:8080/user/{id} # 匹配后路由地址
         predicates: # 断言, 路径相匹配的进行路由
           - Path=/user/{id}
```
* 缺点非常明显，每次改动都需要网关模块重新部署

### 动态路由配置
* 路由信息在Alibaba Nacos中维护，可以实现动态变更
![](./img/动态路由配置原理.png)

Gateway 连接到 Nacos的服务器地址，找到对应的命名空间，再去找到对应的分组，再去找到对应的数据ID，数据Id里面存储的就是路由配置信息，拿到路由配置信息完成解析后再注入到
Gateway里面就完成了路由配置。

动态路由配置是指对 data-id 实现一个监听，如果发生更改以后再去刷新一下当前的路由配置。

GatewayConfig.java 是一个基础，它指定了所有节点的路径信息。
#### 注册网关事件监听器
1. 新建 Java 类 DynamicRouteService，实现事件推送的 Aware。 
